package com.rslai.commons.util.excel;

import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;

import com.rslai.commons.util.StringUtil;
import com.rslai.commons.util.excel.exception.ExcelException;
import com.rslai.commons.util.exception.RunException;
import org.apache.poi.EncryptedDocumentException;
import org.apache.poi.hssf.usermodel.*;
import org.apache.poi.poifs.filesystem.NotOLE2FileException;
import org.apache.poi.poifs.filesystem.OfficeXmlFileException;

/**
 * 读取 Excel 解析类
 */
public class ExcelRead {
    private  int sheet = 0; // 读取第几页sheet，从0开始，默认读取第0个sheet
	private boolean existHead = true; // excel中是否包含头，默认包含
    private int headRow = 0; // 头所在的行号，从0开始，默认第0行是头
    private int bodyRow = 1; // 数据开始的行号，从0开始，默认第1行开始
    private boolean modifBodyRow = false; // 数据开始行号是否改过
    InputStream is = null; // excel的输入流

	private int totalRow; // 总行数
	private int currentRow = 0; // 当前行号
	private String[] headers; // Excel头
	private HSSFSheet hssfSheet = null; // Excel工作表对象
	private HSSFWorkbook hssfWorkbook = null; // Excel对象
    private HSSFRow hssfRow = null; // Excel行对象

    /**
     * 构造方法
     * @param is excel的输入流
     */
	public ExcelRead(InputStream is) {
        this.is = is;
    }

    /**
     * 设置excel中是否包含头
     * @param exist 默认包含头，true 包含头，false 不包含头
     */
    public void setExistHead(boolean exist) {
        this.existHead = exist;
    }

    /**
     * 返回excel中是否包含头
     * @return {@code true} 包含头 {@code false}不含头
     */
    public boolean getExistHead() {
        return this.existHead;
    }

    /**
     * 设置头所在的行号
     * @param row 行号，从0开始，默认第0行是头
     */
    public void setHeadRow(int row) {
        this.headRow = row;
    }

    /**
     * 返回头所在的行号，从0开始
     * @return head的行号
     * @throws Exception Exception
     */
    public int getHeadRow() throws Exception {
        return this.headRow;
    }

    /**
     * 设置数据开始的行号
     * @param row 行号，从0开始，默认第1行开始
     * @throws Exception Exception
     */
    public void setBodyRow(int row) throws Exception {
        if (row <= this.headRow) {
            throw new Exception("数据开始的行号必须大于头所在的行号！");
        }
        this.bodyRow = row;
        this.modifBodyRow = true;
    }

    /**
     * 返回数据开始的行号，从0开始
     * @return 数据开始的行号
     */
    public int getBodyRow() {
        return this.bodyRow;
    }

    /**
     * 设置读取第几页sheet
     * @param number 页号，从0开始，默认读取第0个sheet
     */
    public void setSheet(int number) {
        this.sheet = number;
    }

    /**
     * 返回读取第几页sheet，从0开始
     * @return 正在操作的sheet页号
     */
    public int getSheet() {
        return this.sheet;
    }

    /**
     * 初始化方法
     * @throws Exception
     */
	private void init() throws Exception {
        try {
            this.hssfWorkbook = new HSSFWorkbook(is);

            // 获取excel相应工作表
            if (hssfWorkbook.getNumberOfSheets() >= this.sheet) {
                this.hssfSheet = hssfWorkbook.getSheetAt(this.sheet);
            }

            // 获取总行数
            this.totalRow = hssfSheet.getLastRowNum();
            if ((totalRow==0) && (null==hssfSheet.getRow(0))) {
                throw new Exception("文件为空，请检查！");
            }

            // 获取excel标题
            if (existHead && totalRow >= this.headRow) {
                hssfRow = hssfSheet.getRow(this.headRow);
                int cell = hssfRow.getLastCellNum();
                headers = new String[cell];
                for (int i = 0; i < cell; i++) {
                    headers[i] = this.getString(i);
                }
            }

            // 设置当前行号
            if (this.existHead) {
                if ((this.headRow) >= this.bodyRow) {
                    this.currentRow = headRow + 1;
                } else {
                    this.currentRow = this.bodyRow;
                }
            }else {
                if (modifBodyRow) {
                    this.currentRow = this.bodyRow;
                } else {
                    this.currentRow = 0;
                }
            }
        } catch (EncryptedDocumentException e) {
            throw new Exception("文件打开失败，密码错误！");
        } catch (OfficeXmlFileException e) {
            throw new Exception("文件格式错误，请另存为 Office 2003 及以下版本文件！");
        } catch (NotOLE2FileException e) {
            throw new Exception("文件格式错误，此文件不是一个合法的 Office 文件！");
        }
	}

    /**
     * 返回当前行数据
     * @return Excel行对象
     * @throws Exception Exception
     */
    public ArrayList<String> getCurrentData() throws Exception {
        if (null == hssfRow) {
            throw new Exception("当前行数据为空，请检查是否已正确打开文件！");
        }

        ArrayList<String> list = new ArrayList<String>();
        for (int i=0; i<hssfRow.getLastCellNum(); i++) {
            list.add(getString(i));
        }
        return list;
    }

    /**
     * 返回头数据
     * @return Excel行对象
     * @throws Exception Exception
     */
    public ArrayList<String> getHeadData() throws Exception {
        if (!this.existHead) {
            throw new Exception("无法取得头数据，此文件不包含头信息！");
        } else if (null == headers || headers.length == 0) {
            throw new Exception("文件中第" + this.headRow + "行为头数据，但该行为空！");
        }

        ArrayList<String> list = new ArrayList<String>();
        for (int i=0; i<headers.length; i++) {
            list.add(headers[i]);
        }
        return list;
    }

    /**
     * 将游标从当前位置向前移一行
     * @return {@code true} 成功 {@code false} 失败
     * @throws Exception Exception
     */
	public boolean next() throws Exception {
        if (null == hssfSheet) {
            this.init();
        }
        if (!this.hasNext()) {
            return false;
        }
    	this.hssfRow = this.hssfSheet.getRow(this.currentRow);
		this.currentRow ++;
		return true;
	}

    /**
     * 是否存在下一行
     * @return {@code true} 存在 {@code false} 失败
     * @throws Exception Exception
     */
	public boolean hasNext() throws Exception {
        if (null == hssfSheet) {
            this.init();
        }

        if(this.totalRow >= this.currentRow){
			return true;
		}
		return false;
	}

    /**
     * 返回字段名对应的索引号（模糊匹配只要是字段名开头即可）,未找到抛出异常
     * @param name 字段名
     * @return 返回字段名对应的索引号
     * @throws ExcelException ExcelException
     */
    private int getHeadIndex(String name) throws ExcelException {
        if (null != this.headers) {
            for (int i = 0; i < this.headers.length; i++) {
                if (this.headers[i].indexOf(name) == 0) {
                    return i;
                }
            }
        }
        throw new ExcelException("“" + name + "”字段未找到！");
    }

    /**
     * 返回索引号对应的字段名（未找到返回传入索引号）
     * @param index 索引号
     * @return 返回索引号对应的字段名
     * @throws ExcelException ExcelException
     */
    private String getHeadName(int index) throws ExcelException {
        if ((null!=headers) && (headers.length >= index)) {
            return headers[index];
        } else {
            return index + "";
        }
    }

    /**
     * 返回Excel总行数
     * @return 总行数
     */
	public int getTotalRow() {
		return this.totalRow;
	}

    /**
     * 返回当前行号(heead也计入行数)，从1开始
     * @return 当前行号
     */
	public int getCurrentRow() {
		return this.currentRow;
	}

    /**
     * 根据字段索引号，得到当前字段的 int 类型数据
     * @param index 字段索引，从 0 开始
     * @param notNull 是否允许为空，true不许为空
     * @return int类型数据
     * @throws ExcelException ExcelException
     */
	public int getInt(int index, boolean notNull) throws ExcelException {
        HSSFCell cell = this.hssfRow.getCell(index);

        if (notNull && (null==cell)) {
            throw new ExcelException("“" +  getHeadName(index) + "”不能为空！");
        } else if (!notNull && (null==cell)){
            return 0;
        }

        int ret = 0;
        String str;
        if (null != cell) {
            switch (cell.getCellType()) {
                case HSSFCell.CELL_TYPE_NUMERIC: // 数字
                    ret = (int)cell.getNumericCellValue();
                    break;
                case HSSFCell.CELL_TYPE_STRING: // 字符串
                    str = cell.getStringCellValue();
                    try {
                        if (StringUtil.isBlank(str)) {
                            if (notNull) {
                                throw new ExcelException("“" + getHeadName(index) + "”不能为空！");
                            } else {
                                return 0;
                            }
                        }
                        ret = (int)Double.parseDouble(str);
                    }catch (NumberFormatException e){
                        throw new ExcelException("“" + getHeadName(index) + "”格式错误，“" + str + "”不是整数类型！");
                    }
                    break;
                case HSSFCell.CELL_TYPE_BOOLEAN: // Boolean
                    throw new ExcelException("“" + getHeadName(index) + "”格式错误，“" + cell.getBooleanCellValue()  + "”不是整数类型！");
                case HSSFCell.CELL_TYPE_FORMULA: // 公式
                    cell.setCellType(HSSFCell.CELL_TYPE_STRING);
                    str = cell.getStringCellValue();
                    try {
                        ret = (int)Double.parseDouble(str);
                    }catch (NumberFormatException e){
                        throw new ExcelException("“" + getHeadName(index) + "”格式错误，“" + str + "”不是整数类型！");
                    }
                    break;
                case HSSFCell.CELL_TYPE_BLANK: // 空值
                    if (notNull) {
                        throw new ExcelException("“" +  getHeadName(index) + "”不能为空！");
                    } else {
                        ret = 0;
                    }
                    break;
                case HSSFCell.CELL_TYPE_ERROR: // 故障
                    throw new ExcelException("“" +  getHeadName(index) + "”CELL_TYPE_ERROR！");
                default:
                    throw new ExcelException("“" +  getHeadName(index) + "”未知类型！");
            }
        }
        return ret;
	}

    /**
     * 根据字段索引号，得到当前字段的 int 类型数据，允许为空
     * @param index 字段索引，从 0 开始
     * @return int类型数据
     * @throws ExcelException ExcelException
     */
    public int getInt(int index) throws ExcelException {
        return this.getInt(index, false);
    }

    /**
     * 根据字段名, 得到当前字段的 int 类型数据
     * @param name 字段名
     * @param notNull 是否允许为空，true不许为空
     * @return int类型数据
     * @throws ExcelException ExcelException
     */
	public int getInt(String name, boolean notNull) throws ExcelException {
        return this.getInt(getHeadIndex(name), notNull);
	}

    /**
     * 根据字段名, 得到当前字段的 int 类型数据，允许为空
     * @param name 字段名
     * @return int类型数据
     * @throws ExcelException ExcelException
     */
    public int getInt(String name) throws ExcelException {
       return this.getInt(getHeadIndex(name), false);
    }

    /**
     * 根据字段索引号，得到当前字段的 Double 类型数据
     * @param index 字段索引，从 0 开始
     * @param notNull 是否允许为空，true不许为空
     * @return double类型数据
     * @throws ExcelException ExcelException
     */
	public double getDouble(int index, boolean notNull) throws ExcelException {
        HSSFCell cell = this.hssfRow.getCell(index);

        if (notNull && (null==cell)) {
            throw new ExcelException("“" +  getHeadName(index) + "”不能为空！");
        } else if (!notNull && (null==cell)){
            return 0;
        }

        double ret = 0;
        String str;
        if (null != cell) {
            switch (cell.getCellType()) {
                case HSSFCell.CELL_TYPE_NUMERIC: // 数字
                    ret = cell.getNumericCellValue();
                    break;
                case HSSFCell.CELL_TYPE_STRING: // 字符串
                    str = cell.getStringCellValue();
                    try {
                        if (StringUtil.isBlank(str)) {
                            if (notNull) {
                                throw new ExcelException("“" + getHeadName(index) + "”不能为空！");
                            } else {
                                return 0;
                            }
                        }
                        ret = Double.parseDouble(str);
                    }catch (NumberFormatException e){
                        throw new ExcelException("“" + getHeadName(index) + "”格式错误，“" + str + "”不是数字类型！");
                    }
                    break;
                case HSSFCell.CELL_TYPE_BOOLEAN: // Boolean
                    throw new ExcelException("“" + getHeadName(index) + "”格式错误，“" + cell.getBooleanCellValue()  + "”不是数字类型！");
                case HSSFCell.CELL_TYPE_FORMULA: // 公式
                    cell.setCellType(HSSFCell.CELL_TYPE_STRING);
                    str = cell.getStringCellValue();
                    try {
                        ret = Double.parseDouble(str);
                    }catch (NumberFormatException e){
                        throw new ExcelException("“" + getHeadName(index) + "”格式错误，“" + str + "”不是数字类型！");
                    }
                    break;
                case HSSFCell.CELL_TYPE_BLANK: // 空值
                    if (notNull) {
                        throw new ExcelException("“" +  getHeadName(index) + "”不能为空！");
                    } else {
                        ret = 0;
                    }
                    break;
                case HSSFCell.CELL_TYPE_ERROR: // 故障
                    throw new ExcelException("“" +  getHeadName(index) + "”CELL_TYPE_ERROR！");
                default:
                    throw new ExcelException("“" +  getHeadName(index) + "”未知类型！");
            }
        }
        return ret;
	}

    /**
     * 根据字段名, 得到当前字段的 Double 类型数据
     * @param name 字段名
     * @param notNull 是否允许为空，true不许为空
     * @return double类型数据
     * @throws ExcelException ExcelException
     */
	public double getDouble(String name, boolean notNull) throws ExcelException {
        return this.getDouble(getHeadIndex(name), notNull);
	}

    /**
     * 根据字段索引号，得到当前字段的 Double 类型数据，允许为空
     * @param index 字段索引，从 0 开始
     * @return double类型数据
     * @throws ExcelException ExcelException
     */
    public double getDouble(int index) throws ExcelException {
        return this.getDouble(index, false);
    }

    /**
     * 根据字段名, 得到当前字段的 Double 类型数据，允许为空
     * @param name 字段名
     * @return double类型数据
     * @throws ExcelException ExcelException
     */
    public double getDouble(String name) throws ExcelException {
        return this.getDouble(getHeadIndex(name), false);
    }

    /**
     * 根据字段索引号，得到当前字段的 String 类型数据，允许为空
     * @param index 字段索引，从 0 开始
     * @return String类型数据
     * @throws ExcelException ExcelException
     */
    public String getString(int index) throws ExcelException {
        return getString(index, false);
    }

    /**
     * 根据字段索引号，得到当前字段的 String 类型数据
     * @param index 字段索引，从 0 开始
     * @param notNull 是否允许为空，true不许为空
     * @return String类型数据
     * @throws ExcelException ExcelException
     */
	public String getString(int index, boolean notNull) throws ExcelException {
        HSSFCell cell = this.hssfRow.getCell(index);

        if (notNull && (null==cell)) {
            throw new ExcelException("“" +  getHeadName(index) + "”不能为空！");
        } else if (!notNull && (null==cell)){
            return "";
        }

        String str = "";
        if (null != cell) {
            switch (cell.getCellType()) {
                case HSSFCell.CELL_TYPE_NUMERIC: // 数字
                    if (HSSFDateUtil.isCellDateFormatted(cell)) {
                        str = StringUtil.parseToString(org.apache.poi.ss.usermodel.DateUtil.getJavaDate(cell.getNumericCellValue()));
                    } else {
                        cell.setCellType(HSSFCell.CELL_TYPE_STRING);
		                str = cell.getStringCellValue();
                        if (notNull && StringUtil.isBlank(str)) {
                            throw new ExcelException("“" +  getHeadName(index) + "”不能为空！");
                        }
                    }
                    break;
                case HSSFCell.CELL_TYPE_STRING: // 字符串
                    str = cell.getStringCellValue().trim();
                    if (notNull && StringUtil.isBlank(str)) {
                        throw new ExcelException("“" +  getHeadName(index) + "”不能为空！");
                    }
                    break;
                case HSSFCell.CELL_TYPE_BOOLEAN: // Boolean
                    cell.setCellType(HSSFCell.CELL_TYPE_STRING);
                    str = cell.getStringCellValue();
                case HSSFCell.CELL_TYPE_FORMULA: // 公式
                    cell.setCellType(HSSFCell.CELL_TYPE_STRING);
                    str = cell.getStringCellValue();
                    if (notNull && StringUtil.isBlank(str)) {
                        throw new ExcelException("“" +  getHeadName(index) + "”不能为空！");
                    }
                    break;
                case HSSFCell.CELL_TYPE_BLANK: // 空值
                    str = "";
                    if (notNull && StringUtil.isBlank(str)) {
                        throw new ExcelException("“" +  getHeadName(index) + "”不能为空！");
                    }
                    break;
                case HSSFCell.CELL_TYPE_ERROR: // 故障
                    throw new ExcelException("“" +  getHeadName(index) + "”CELL_TYPE_ERROR！");
                default:
                    throw new ExcelException("“" +  getHeadName(index) + "”未知类型！");
            }
        }
        return str;
	}

    /**
     * 根据字段名, 得到当前字段的 String 类型数据，允许为空
     * @param name 字段名
     * @return String类型数据
     * @throws ExcelException ExcelException
     */
    public String getString(String name) throws ExcelException {
        return this.getString(getHeadIndex(name), false);
    }

    /**
     * 根据字段名, 得到当前字段的 String 类型数据
     * @param name 字段名
     * @param notNull 是否允许为空，true不许为空
     * @return String类型数据
     * @throws ExcelException ExcelException
     */
    public String getString(String name, boolean notNull) throws ExcelException {
        return this.getString(getHeadIndex(name), notNull);
    }

    /**
     * 根据字段名, 得到当前字段的 Date 类型数据
     * @param name 字段名
     * @param notNull 是否允许为空，true不许为空，false允许空则可能返回null
     * @return String类型数据
     * @throws ExcelException ExcelException
     */
    public Date getDateTime(String name, boolean notNull) throws ExcelException {
        return this.getDateTime(getHeadIndex(name), notNull);
    }

    /**
     * 根据字段名, 得到当前字段的 Date 类型数据，允许为空
     * @param name 字段名
     * @return String类型数据
     * @throws ExcelException ExcelException
     */
    public Date getDateTime(String name) throws ExcelException {
        return this.getDateTime(getHeadIndex(name), false);
    }

    /**
     * 根据字段索引号，得到当前字段的 Date 类型数据
     * @param index 字段索引，从 0 开始
     * @param notNull 是否允许为空，true不许为空，false允许空则可能返回null
     * @return Date类型数据
     * @throws ExcelException ExcelException
     */
    public Date getDateTime(int index, boolean notNull) throws ExcelException {
        HSSFCell cell = this.hssfRow.getCell(index);

        if (notNull && (null==cell)) {
            throw new ExcelException("“" +  getHeadName(index) + "”不能为空！");
        } else if (!notNull && (null==cell)){
            return null;
        }

        Date ret = null;
        if (null != cell) {
            switch (cell.getCellType()) {
                case HSSFCell.CELL_TYPE_NUMERIC: // 数字
                    ret = org.apache.poi.ss.usermodel.DateUtil.getJavaDate(cell.getNumericCellValue());
                    break;
                case HSSFCell.CELL_TYPE_STRING: // 字符串
                    try {
                        String str = cell.getStringCellValue().trim();
                        if (StringUtil.isBlank(str)) {
                            if (notNull) {
                                throw new ExcelException("“" + getHeadName(index) + "”不能为空！");
                            } else {
                                return null;
                            }
                        }
                        ret = StringUtil.parseToDate(str);
                    } catch (ExcelException e) {
                        throw new ExcelException("“" + getHeadName(index) + "”格式错误，“" + cell.getStringCellValue()  + "”不是日期时间类型！");
                    }
                    break;
                case HSSFCell.CELL_TYPE_BOOLEAN: // Boolean
                    throw new ExcelException("“" + getHeadName(index) + "”格式错误，“" + cell.getBooleanCellValue()  + "”不是日期时间类型！");
                case HSSFCell.CELL_TYPE_FORMULA: // 公式
                    cell.setCellType(HSSFCell.CELL_TYPE_STRING);
                    try {
                        ret = StringUtil.parseToDate(cell.getStringCellValue());
                    } catch (RunException e) {
                        throw new ExcelException("“" + getHeadName(index) + "”格式错误，“" + cell.getStringCellValue()  + "”不是日期时间类型！");
                    }
                    break;
                case HSSFCell.CELL_TYPE_BLANK: // 空值
                    if (notNull) {
                        throw new ExcelException("“" +  getHeadName(index) + "”不能为空！");
                    } else {
                        ret = null;
                    }
                    break;
                case HSSFCell.CELL_TYPE_ERROR: // 故障
                    throw new ExcelException("“" +  getHeadName(index) + "”CELL_TYPE_ERROR！");
                default:
                    throw new ExcelException("“" +  getHeadName(index) + "”未知类型！");
            }
        }
        return ret;
    }

    /**
     * 根据字段索引号, 得到当前字段的 Date 类型数据，允许为空
     * @param index 字段索引，从 0 开始
     * @return Date类型数据
     * @throws ExcelException ExcelException
     */
    public Date getDateTime(int index) throws ExcelException {
        return this.getDateTime(index, false);
    }

    @Override
    public String toString() {
        final StringBuilder sb = new StringBuilder("ExcelRead{");
        sb.append("sheet=").append(sheet);
        sb.append(", existHead=").append(existHead);
        sb.append(", headRow=").append(headRow);
        sb.append(", bodyRow=").append(bodyRow);
        sb.append(", totalRow=").append(totalRow);
        sb.append(", currentRow=").append(currentRow);
        sb.append(", headers=").append(Arrays.toString(headers));
        sb.append('}');
        return sb.toString();
    }

/*
    static public void main(String[] args) {
        InputStream is = null;
        try {
            is = new FileInputStream("d:\\2.xls");

            ExcelRead excelRead = new ExcelRead(is);

            excelRead.hasNext();
            System.out.println(excelRead.getHeadData());

            while (excelRead.hasNext()) {
                try {
                    excelRead.next();

                    System.out.println(excelRead.getString(0));
                    System.out.println(excelRead.getString("姓名"));

                    System.out.println(excelRead.getDateTime(4, true));
                    System.out.println(excelRead.getDateTime("身份证日期"));

                    System.out.println(excelRead.getString(5));
                    System.out.println(excelRead.getString("身份证号"));

                    System.out.println(excelRead.getInt("年龄"));

                    System.out.println("当前行号：" + excelRead.getCurrentRow());

                    System.out.println("=====================");
                } catch (ExcelException e) {
                    System.out.println(e.getMessage());
                }
            }
            System.out.println(excelRead.toString());
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (null != is) {
                try {
                    is.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
*/

}
